/* ===================

AX2E (AXESCON XACML 2.0 Engine) is the Java authorization engine, which implements OASIS XACML 2.0 standard.
Copyright (C) 2007 AXESCON LLC

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA


Contact: AXESCON LLC - info{at}axescon.com

 =================== */
package axescon.xacml.engine;

import axescon.xacml.FeatureDisabler;
import axescon.xacml.api.*;
import axescon.xacml.model.ctx.Result;
import axescon.xacml.model.ctx.XacmlCtx;
import axescon.xacml.model.ctx.impl.ResultImpl;
import axescon.xacml.model.policy.Condition;
import axescon.xacml.model.policy.Rule;
import axescon.xacml.model.shared.Decision;
import axescon.xacml.model.shared.attr.BooleanAttribute;
import axescon.xacml.util.LoggerUtil;

import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author argyn
 *         Date: Jun 25, 2005
 *         Time: 2:16:46 PM
 *         Responsibilities: represents XACML PDP
 */
public class RuleEvaluator {


    private static Logger logger = Logger.getLogger("axescon.xacml.engine.RuleEvaluator");

    /**
     * this should implement axescon.xacml 2.0 spec, ch. 7.9. Rule evaluation
     *
     * @param rule
     * @param ch
     * @param req
     * @return result, which may contain an original exception, if the result is indeterminate
     */
    public Result eval(Rule rule, CtxHandler ch, XacmlCtx req) throws XacmlException {
        ResultImpl ret = new ResultImpl();
        boolean tmatched = false;


        if (FeatureDisabler.ENABLE_EXPLAIN && req.getOptions().EXPLAIN) {
            req.addStep("Rule id:" + rule.getRuleId());
        }

        try {
            tmatched = getTargetMatcher().match(rule.getTarget(), req, ch);

            if (FeatureDisabler.ENABLE_EXPLAIN && req.getOptions().EXPLAIN) {
                req.addSentence(" Target Match: " + tmatched);
            }

        } catch (TargetMatchIndeterminateException e) {
            // 5th line in truth table (TT)
            ret.setDecision(Decision.BYTE_INDETERMINATE);


            if (FeatureDisabler.ENABLE_EXPLAIN && req.getOptions().EXPLAIN) {
                req.addSentence(" Target Match Indeterminate");
                req.toParentStep();
            }
            if (logger.isLoggable(Level.FINER)) {
                logger.finer("Target Match Indeterminate " + LoggerUtil.getLogString(rule));
            }
            ret.setXacmlException(e);
            ret.setStatus(e.getStatus());

            return ret;
        }

        if (!tmatched) { // 4th line in  TT
            ret.setDecision(Decision.BYTE_NOT_APPLICABLE);
        } else {
            // if matched, then first 3 lines in truth table

            try {

                if (eval(rule.getCondition(), ch, req)) {
                    // 1st line in TT
                    ret.setDecision(rule.getEffect());
                } else {
                    // 2nd lne in TT
                    ret.setDecision(Decision.BYTE_NOT_APPLICABLE);
                }

            } catch (ExpressionEvalIndeterminateException e) {//3rd line in TT
                e.setRuleId(rule.getRuleId());

                ret.setDecision(Decision.BYTE_INDETERMINATE);

                ret.setStatus(e.getStatus());

                if (logger.isLoggable(Level.FINER)) {
                    logger.finer("Condition evaluation indeterminate in " + LoggerUtil.getLogString(rule));
                }
                ret.setXacmlException(e);
            }
        }
        if (FeatureDisabler.ENABLE_EXPLAIN && req.getOptions().EXPLAIN) {
            req.addSentence(" Decision:" + ret.getDecisionString());
            req.toParentStep();
        }
        return ret;
    }


    /**
     * this should implement axescon.xacml 2.0 spec, ch. 7.8. Condition evaluation
     *
     * @param cond
     * @param ch
     * @param req
     * @return
     * @throws ExpressionEvalIndeterminateException
     *
     */
    public boolean eval(Condition cond, CtxHandler ch, XacmlCtx req) throws
            XacmlException, ExpressionEvalIndeterminateException {
        if (cond == null) return true;

        // todo: check that function id is supposed to return boolean
        Liquid rets = cond.getExpression().eval(ch, req, getExpressionEvaluator());

        if (!(rets.getSingular() instanceof BooleanAttribute))
            throw new ExpressionEvalIndeterminateException("non boolean result from condition: " + rets.getValue()
                    , XacmlException.ERR_CODE_SYNTAX_CONDITION_NON_BOOLEAN);

        boolean ret = ((BooleanAttribute) rets.getSingularUnsafe()).getValue();
        return ret;
    }

    private TargetMatcher targetMatcher;

    public TargetMatcher getTargetMatcher() {
        return targetMatcher;
    }

    public void setTargetMatcher(TargetMatcher targetMatcher) {
        this.targetMatcher = targetMatcher;
    }


    private ExpressionEvaluator expressionEvaluator;

    public ExpressionEvaluator getExpressionEvaluator() {
        return expressionEvaluator;
    }

    public void setExpressionEvaluator(ExpressionEvaluator expressionEvaluator) {
        this.expressionEvaluator = expressionEvaluator;
    }

    public void setDefaults() throws XacmlException {
        setTargetMatcher(new TargetMatcherImpl(true));
        setExpressionEvaluator(new ExpressionEvaluatorImpl(true));
    }

    public RuleEvaluator() throws XacmlException {
        this(false);
    }

    public RuleEvaluator(boolean setDefaults) throws XacmlException {
        if (setDefaults) setDefaults();
    }

    /**
     * this method validates syntax of a condition
     * @param cond
     * @throws XacmlException if syntax error is found
     */
    public void validate(Condition cond) throws XacmlException{
        if (cond != null &&
                !BooleanAttribute.class.isAssignableFrom(
                        cond.getExpression().getReturnDataType(getExpressionEvaluator().getFnFactory()))) {
            throw new ExpressionEvalIndeterminateException("non boolean function in condition"
                    , XacmlException.ERR_CODE_SYNTAX_CONDITION_NON_BOOLEAN);
        }

    }

    /**
     * this method validates syntax of a rule
     * @param rule
     * @throws XacmlException if syntax error is found
     */
    public void validate(Rule rule) throws XacmlException{
        try {
            validate(rule.getCondition());
        } catch (ExpressionEvalIndeterminateException e) {
            e.setRuleId(rule.getRuleId());
            throw e;
        }
    }

}
